package examples.jsf.component;

import examples.jsf.Application;
import examples.jsf.ConverterException;
import examples.jsf.context.FacesContext;
import examples.jsf.event.PostValidateEvent;
import examples.jsf.event.PreValidateEvent;

import javax.el.ELException;
import javax.el.ValueExpression;
import java.util.Iterator;
import java.util.List;

/**
 * @author Bill
 * @version 2013-05-29
 */
public class UIInput extends UIOutput implements EditableValueHolder {

    public boolean isImmediate() {
        return false;
    }

    public boolean isValid() {
        return false;
    }

    public void setValid(boolean valid) {
    }

    public boolean isRequired(){
        return false;
    }

    // 这里new value 只是存到了StateHelper中，并没有反应到 value所绑定的表达式中，设置了
    // isLocalValueSet 变量以备下一生命周期阶段(UpdateModelPhase) 判断
    public void setValue(Object value) {
        super.setValue(value);
        // Mark the local value as set.
        setLocalValueSet(true);
    }

    public void setSubmittedValue(Object submittedValue) {}

    @Override
    public void processValidators(FacesContext context) {
        if (context == null) {
            throw new NullPointerException();
        }

        // Skip processing if our rendered flag is false
        if (!isRendered()) {
            return;
        }

        pushComponentToEL(context, this);

        if (!isImmediate()) {
            Application application = context.getApplication();
            application.publishEvent(context, PreValidateEvent.class, this);
            executeValidate(context);
            application.publishEvent(context, PostValidateEvent.class, this);
        }
        for (Iterator<UIComponent> i = getFacetsAndChildren(); i.hasNext(); ) {
            i.next().processValidators(context);
        }

        popComponentFromEL(context);
    }

    /**
     * Executes validation logic.
     */
    private void executeValidate(FacesContext context) {
        try {
            validate(context);
        } catch (RuntimeException e) {
            context.renderResponse();
            throw e;
        }

        if (!isValid()) {
            context.validationFailed();
            context.renderResponse();
        }
    }

    // 覆盖此方法的不多， 主要是PrimeFaces组件
    //     Calendar, TimePicker, Rating 等
    public void validate(FacesContext context) {
        // get submitted value
        Object submittedValue = null;

        // Submitted value == null means "the component was not submitted
        // at all".
        if (submittedValue == null) {
            return;
        }

        // converter submitted value to new value
        Object newValue = null;

        // validate new value
        validateValue(context, newValue);

        // if validation passed
        if (isValid()) {
            // store new value to component
            // set submitted value as null
            // if value changed, queued a ValueChangeEvent
        }
    }

    // 这里提供对Input类型（EditableValueHolder）的一般实现
    // 覆盖的子类有
    //   UISelectOne, UISelectMany, SelectOneMenu(PrimeFaces) 等
    protected void validateValue(FacesContext context, Object newValue) {

        // first, check if empty
        if (isValid() && isRequired() && isEmpty(newValue)) {
            // validation failed. create FacesMessage
            setValid(false);
        }

        // 如果valid，并且new value不为空
        if (isValid() && (!isEmpty(newValue))) {
            // 循环遍历validators
            //      读取this的validator数组，遍历调用
            //      如果validator not passed, create a message，继续遍历调用
            // 循环遍历end
        }

    }

    // 先委托renderer，如果不存在renderer,就按type自动匹配Converter
    protected Object getConvertedValue(FacesContext context,
                                       Object newSubmittedValue) throws ConverterException {
        // if (renderer) {renderer.getConverteredValue() } else {
        // }
        return null;
    }


    // 此方法好像没有组件直接覆盖，一般还是以覆盖updateModel方法为主
    public void processUpdates(FacesContext context) {

        if (context == null) {
            throw new NullPointerException();
        }

        // Skip processing if our rendered flag is false
        if (!isRendered()) {
            return;
        }

        super.processUpdates(context);

        try {
            updateModel(context);
        } catch (RuntimeException e) {
            context.renderResponse();
            throw e;
        }

        if (!isValid()) {
            context.renderResponse();
        }
    }

    // 这里也是通用方法，覆盖此方法的子类寥寥(UIViewParameter)，
    public void updateModel(FacesContext context) {

        if (context == null) {
            throw new NullPointerException();
        }

        if (!isValid() || !isLocalValueSet()) {
            return;
        }
        ValueExpression ve = getValueExpression("value");

        if (ve != null) {
            Throwable caught = null;
            try {
                ve.setValue(context.getELContext(), getLocalValue());
                setValue(null);
                setLocalValueSet(false); // 值已经刷新到VE中，存储的value可以删除了
            }
            catch (ELException e) {
                // 设值给 ValueExpression 发生EL异常
                caught = e;
                setValid(false);
            }
            catch (Exception e) {
                // 其他异常
                caught = e;
                setValid(false);
            }

            if (null != caught) {
                // queue Exception
            }
        }

    }


    public static boolean isEmpty(Object value) {

        if (value == null) {
            return (true);
        } else if ((value instanceof String) &&
                (((String) value).length() < 1)) {
            return (true);
        } else if (value.getClass().isArray()) {
            if (0 == java.lang.reflect.Array.getLength(value)) {
                return (true);
            }
        } else if (value instanceof List) {
            if (((List) value).isEmpty()) {
                return (true);
            }
        }
        return (false);
    }

    /**
     * Return the "local value set" state for this component.
     * Calls to <code>setValue()</code> automatically reset
     * this property to <code>true</code>.
     */
    public boolean isLocalValueSet() {
        return (Boolean) getStateHelper().eval("localValueSet", false);
    }

    /**
     * Sets the "local value set" state for this component.
     */
    public void setLocalValueSet(boolean localValueSet) {
        getStateHelper().put("localValueSet", localValueSet);
    }


    /**
     * <p>Convenience method to reset this component's value to the
     * un-initialized state.  This method does the following:</p>
     * <p/>
     * <p>Call {@link #setValue} passing <code>null</code>.</p>
     * <p/>
     * <p>Call {@link #setSubmittedValue} passing <code>null</code>.</p>
     * <p/>
     * <p>Call {@link #setLocalValueSet} passing <code>false</code>.</p>
     * <p/>
     * <p>Call {@link #setValid} passing <code>true</code>.</p>
     * <p/>
     * <p>Upon return from this call if the instance had a
     * <code>ValueBinding</code> associated with it for the "value"
     * property, this binding is evaluated when {@link
     * UIOutput#getValue} is called.  Otherwise, <code>null</code> is
     * returned from <code>getValue()</code>.</p>
     */
    public void resetValue() {
        this.setValue(null);
        this.setSubmittedValue(null);
        this.setLocalValueSet(false);
        this.setValid(true);
    }

}
